"use client"

import {
   AnimatePresence,
   type MotionProps,
   type Variants,
   motion,
} from "motion/react"
import type { ElementType } from "react"
import { ny } from "~/registry/miami/lib/utils"

type AnimationType = "text" | "word" | "character" | "line"
type AnimationVariant =
   | "fadeIn"
   | "blurIn"
   | "blurInUp"
   | "blurInDown"
   | "slideUp"
   | "slideDown"
   | "slideLeft"
   | "slideRight"
   | "scaleUp"
   | "scaleDown"

interface TextAnimateProps extends MotionProps {
   /**
    * The text content to animate
    */
   children: string
   /**
    * The class name to be applied to the component
    */
   className?: string
   /**
    * The class name to be applied to each segment
    */
   segmentClassName?: string
   /**
    * The delay before the animation starts
    */
   delay?: number
   /**
    * The duration of the animation
    */
   duration?: number
   /**
    * Custom motion variants for the animation
    */
   variants?: Variants
   /**
    * The element type to render
    */
   as?: ElementType
   /**
    * How to split the text ("text", "word", "character")
    */
   by?: AnimationType
   /**
    * Whether to start animation when component enters viewport
    */
   startOnView?: boolean
   /**
    * Whether to animate only once
    */
   once?: boolean
   /**
    * The animation preset to use
    */
   animation?: AnimationVariant
}

const staggerTimings: Record<AnimationType, number> = {
   text: 0.06,
   word: 0.05,
   character: 0.03,
   line: 0.06,
}

const defaultContainerVariants = {
   hidden: { opacity: 1 },
   show: {
      opacity: 1,
      transition: {
         staggerChildren: 0.05,
      },
   },
   exit: {
      opacity: 0,
      transition: {
         staggerChildren: 0.05,
         staggerDirection: -1,
      },
   },
}

const defaultItemVariants: Variants = {
   hidden: { opacity: 0 },
   show: {
      opacity: 1,
   },
   exit: {
      opacity: 0,
   },
}

const defaultItemAnimationVariants: Record<
   AnimationVariant,
   { container: Variants; item: Variants }
> = {
   fadeIn: {
      container: defaultContainerVariants,
      item: {
         hidden: { opacity: 0, y: 20 },
         show: (delay: number) => ({
            opacity: 1,
            y: 0,
            transition: {
               delay,
               duration: 0.3,
            },
         }),
         exit: {
            opacity: 0,
            y: 20,
            transition: { duration: 0.3 },
         },
      },
   },
   blurIn: {
      container: defaultContainerVariants,
      item: {
         hidden: { opacity: 0, filter: "blur(10px)" },
         show: (i: number) => ({
            opacity: 1,
            filter: "blur(0px)",
            transition: {
               delay: i * 0.1,
               duration: 0.3,
            },
         }),
         exit: {
            opacity: 0,
            filter: "blur(10px)",
            transition: { duration: 0.3 },
         },
      },
   },
   blurInUp: {
      container: defaultContainerVariants,
      item: {
         hidden: { opacity: 0, filter: "blur(10px)", y: 20 },
         show: (delay: number) => ({
            opacity: 1,
            filter: "blur(0px)",
            y: 0,
            transition: {
               y: { duration: 0.3 },
               opacity: { duration: 0.4 },
               filter: { duration: 0.3 },
            },
         }),
         exit: {
            opacity: 0,
            filter: "blur(10px)",
            y: 20,
            transition: {
               y: { duration: 0.3 },
               opacity: { duration: 0.4 },
               filter: { duration: 0.3 },
            },
         },
      },
   },
   blurInDown: {
      container: defaultContainerVariants,
      item: {
         hidden: { opacity: 0, filter: "blur(10px)", y: -20 },
         show: (delay: number) => ({
            opacity: 1,
            filter: "blur(0px)",
            y: 0,
            transition: {
               y: { duration: 0.3 },
               opacity: { duration: 0.4 },
               filter: { duration: 0.3 },
            },
         }),
      },
   },
   slideUp: {
      container: defaultContainerVariants,
      item: {
         hidden: { y: 20, opacity: 0 },
         show: (delay: number) => ({
            y: 0,
            opacity: 1,
            transition: {
               delay,
               duration: 0.3,
            },
         }),
         exit: {
            y: -20,
            opacity: 0,
            transition: {
               duration: 0.3,
            },
         },
      },
   },
   slideDown: {
      container: defaultContainerVariants,
      item: {
         hidden: { y: -20, opacity: 0 },
         show: {
            y: 0,
            opacity: 1,
            transition: { duration: 0.3 },
         },
         exit: {
            y: 20,
            opacity: 0,
            transition: { duration: 0.3 },
         },
      },
   },
   slideLeft: {
      container: defaultContainerVariants,
      item: {
         hidden: { x: 20, opacity: 0 },
         show: {
            x: 0,
            opacity: 1,
            transition: { duration: 0.3 },
         },
         exit: {
            x: -20,
            opacity: 0,
            transition: { duration: 0.3 },
         },
      },
   },
   slideRight: {
      container: defaultContainerVariants,
      item: {
         hidden: { x: -20, opacity: 0 },
         show: {
            x: 0,
            opacity: 1,
            transition: { duration: 0.3 },
         },
         exit: {
            x: 20,
            opacity: 0,
            transition: { duration: 0.3 },
         },
      },
   },
   scaleUp: {
      container: defaultContainerVariants,
      item: {
         hidden: { scale: 0.5, opacity: 0 },
         show: {
            scale: 1,
            opacity: 1,
            transition: {
               duration: 0.3,
               scale: {
                  type: "spring",
                  damping: 15,
                  stiffness: 300,
               },
            },
         },
         exit: {
            scale: 0.5,
            opacity: 0,
            transition: { duration: 0.3 },
         },
      },
   },
   scaleDown: {
      container: defaultContainerVariants,
      item: {
         hidden: { scale: 1.5, opacity: 0 },
         show: (delay: number) => ({
            scale: 1,
            opacity: 1,
            transition: {
               delay,
               duration: 0.3,
               scale: {
                  type: "spring",
                  damping: 15,
                  stiffness: 300,
               },
            },
         }),
         exit: {
            scale: 1.5,
            opacity: 0,
            transition: { duration: 0.3 },
         },
      },
   },
}

export function TextAnimate({
   children,
   delay = 0,
   duration = 0.3,
   variants,
   className,
   segmentClassName,
   as: Component = "p",
   startOnView = true,
   once = false,
   by = "word",
   animation = "fadeIn",
   ...props
}: TextAnimateProps) {
   const MotionComponent = motion.create(Component)

   // Use provided variants or default variants based on animation type
   const finalVariants = animation
      ? {
           container: {
              ...defaultItemAnimationVariants[animation].container,
              show: {
                 ...defaultItemAnimationVariants[animation].container.show,
                 transition: {
                    staggerChildren: staggerTimings[by],
                 },
              },
              exit: {
                 ...defaultItemAnimationVariants[animation].container.exit,
                 transition: {
                    staggerChildren: staggerTimings[by],
                    staggerDirection: -1,
                 },
              },
           },
           item: defaultItemAnimationVariants[animation].item,
        }
      : { container: defaultContainerVariants, item: defaultItemVariants }

   let segments: string[] = []
   switch (by) {
      case "word":
         segments = children.split(/(\s+)/)
         break
      case "character":
         segments = children.split("")
         break
      case "line":
         segments = children.split("\n")
         break
      case "text":
      default:
         segments = [children]
         break
   }

   return (
      <AnimatePresence mode="popLayout">
         <MotionComponent
            variants={finalVariants.container}
            initial="hidden"
            whileInView={startOnView ? "show" : undefined}
            animate={startOnView ? undefined : "show"}
            exit="exit"
            className={ny("whitespace-pre-wrap", className)}
            {...props}
         >
            {segments.map((segment, i) => (
               <motion.span
                  key={`${by}-${segment}-${i}`}
                  variants={finalVariants.item}
                  custom={i * staggerTimings[by]}
                  className={ny(
                     by === "line" ? "block" : "inline-block whitespace-pre",
                     segmentClassName,
                  )}
               >
                  {segment}
               </motion.span>
            ))}
         </MotionComponent>
      </AnimatePresence>
   )
}
